# Springnote API Libraray (ver 0.1)
#
# Get/Put/Create/Delete page in Springnote(http://springnote.com)
#
# Author: ikspres (mail@ikspres.com)
# Modified: 2007.06.25
# License: Same as Ruby License
#
require 'rubygems'
require 'cgi'
require 'sha1'
require 'hpricot'
require 'rest-open-uri'
require 'builder'


#
#
#== Set personal access information of account and application.
#== CAUTION! Before calling this, all the following functions will fail
#
#  Rspringnote::set_credential( 'your_open_id.xxx.com', 
#			      'your user key',
#			      'your app id',
#			      'your app key')
# 
#== Get page of which page_id is 1234 
#
#  page = Rspringnote::Page.new :identifier => '1234'
#  page.get
#
#== Update page body 
#  
#  page = Rspringnote::Page.new :identifier => '1234'
#  page.get
#  page.source = 'new body content'
#  page.put
#
#== Create a new page 
#  page = Rspringnote::Page.new(:title => 'new page title', 
#                              :source = 'new page body')
#
#  page.identifier    # ==> nil
#  page.create
#  page.identifier    # ==> 1235  or the id of the new page 
#
#== Delete a new page 
#
#  page = Rspringnote::Page.new :identifier => '1234'
#  page.delete
#
#== Search pages
#  pages = Rspringnote::Page.search('hello')  # search 'hello', full text search is off    
#  pages = Rspringnote::Page.search('hello', true)  # search 'hello', full text search  is on
#
#  pages[0].identifer   
#  pages[0].title   
#
#  pages[0].source      # NOTICE!! When source is accessed, pages[0].get is called automatically
#                                  because Page.search() return only identifiers and titles
#
#

module Rspringnote
    def self.set_credential(open_id, user_key, app_id, app_key)
        API.set_credential(open_id, user_key, app_id, app_key)
    end

    class API
	DOMAIN = 'api.springnote.com'
	PORT = 80

        @@config = nil
        @@username = nil
        @@password = nil

        class << self
	    def set_credential(open_id, user_key, app_id, app_key)
	       @@config = {
	           'open_id' => open_id,
		   'user_key' => user_key,
		   'app_id' => app_id,
		   'app_key' => app_key }
	    end

            def config
		raise "Call Rspringnote::set_credentail to set your id and keys " unless @@config
                @@config 
            end

            def set_username
                @@username = CGI.escape(config['open_id'])
            end
            def set_password
                @@password = CGI.escape(digestkey)
            end
        

            def username
                @@username || @@username = set_username
            end

            def password
                @@password || @@password = set_password
            end

            def password=(pw)
                @@password = pw
            end



            # this code is copied from ias's sample source in springnote API guide
            def digestkey
                nonce = sprintf("%016x", rand(16 ** 16))
                time = Time.now.utc.xmlschema #'2007-02-14T20:00:00Z' 
                key1 = SHA1.sha1(nonce + time + config['user_key'] + config['app_key']).to_s
                config['app_id'].to_s + ',' + nonce + ',' + time + ',' + key1
            end

            def page_url(page_id)
                page_path = "/pages/#{page_id.to_s}.xml"
                url = "http://#{DOMAIN}#{page_path}"
            end

            def pages_url
                page_path = "/pages.xml"
                url = "http://#{DOMAIN}#{page_path}"
            end

            def search_url(keyword, fulltext_search)
                search_path = "/pages/search.xml"
                url = "http://#{DOMAIN}#{search_path}?q=#{keyword}&fulltext=#{fulltext_search ? '1' : '0' }"
            end

            def do_open(url, options = {})
                options = options.merge({:http_basic_authentication => [username, password]})
#                puts url
#                puts options.inspect
                open(url, options)
            end

            def search_pages(keyword, fulltext_search)
                do_open(search_url(keyword, fulltext_search))
            end

            # get the specified page content 
            # return as string, and save as a file if filepath is given.
            def get_page(page_id, filepath = nil)
                do_open(page_url(page_id))
            end

            # input may be a string of xml or a filepath to a xml file
            def put_page(page_id, input)
                body = File.exists?(input) ? CGI.escapgeHTML(File.read(input)) : input
                do_open(page_url(page_id), :method => :put, :body => body)
            end

            def create_page(xml)
                do_open(pages_url, :method => :post, :body => xml)
            end

            def delete_page(page_id)
                do_open(page_url(page_id), :method => :delete)
            end
        end
    end
end

module Rspringnote
    class Page
	attr_accessor :title, :identifier, :source, :relation_is_part_of, :need_get
        attr_reader :api

        class << self
            def search(keyword, fulltext_search = false)
                 parse_pages API.search_pages(keyword, fulltext_search)
            end
        end


	def initialize(options={})
	    if (doc = options[:hpricot])
		@title = doc.at('title').inner_html
		@identifier = doc.at('identifier').inner_html
		@source = doc.at('source').inner_html
	    end

            @need_get = false

	    options.delete :hpricot
	    options.each {|k,v| eval "@#{k.to_s} = v.to_s" if self.respond_to? k }
	end

        class << self
            def parse_pages(xml)
                doc = Hpricot xml

                page_docs = doc.search '//page'

                pages = page_docs.map do |page_doc|
                    page = Page.new
                    page.identifier = page_doc.at('id').inner_html if page_doc.at('id')
                    page.title = page_doc.at('name').inner_html if page_doc.at('name')
                    page.need_get = true
                    page
                end
            end
        end
        def parse(xml)
	    doc = Hpricot xml
            attributes = %w{title identifier source}
            attributes.each do |attrib|
                eval "@#{attrib} = doc.at('#{attrib}').inner_html if doc.at('#{attrib}')"
            end
        end

        def need_get?
            @need_get
        end

        def source
            if need_get?
                get 
                @need_get = false
            end
            @source
        end

        def get
            parse API.get_page(@identifier)
        end

        def put
            parse API.put_page(@identifier, to_xml)
        end

        def create
            parse API.create_page(to_xml)
        end

        def delete
            parse API.delete_page(@identifier)
        end

	def to_xml
	    builder = Builder::XmlMarkup.new
	    builder.instruct! :xml, :version => '1.0', :encoding=> 'UTF-8'

	    xml = builder.page do |b|
		b.title(title) if title
		b.source(source) if source
		b.identifier(identifier) if identifier
		b.__send__ 'relation-is-part-of', relation_is_part_of if relation_is_part_of
	    end 
	end
    end
end
